/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "spatial_napi.h"

#include <securec.h>
#include <uv.h>
#include "nlohmann/json.hpp"
#include "ispatial_location_callback.h"
#include "spatial_awareness_common.h"

using namespace OHOS::Msdp;
namespace {
#define GET_PARAMS(env, info, num)      \
    size_t argc = num;                  \
    napi_value args[num] = { nullptr }; \
    napi_value jsthis = nullptr;       \
    void *data = nullptr;               \
    NAPI_CALL(env, napi_get_cb_info(env, info, &argc, args, &jsthis, &data))

SpatialNapi* g_spatialNapi; // spatialNapi manager
std::map<std::string, SpatialLocationMode> g_ModeMapping;
std::map<DirectionType, std::string> g_DrectionMap;
constexpr int32_t NAPI_ARGS_ZERO = 0;
constexpr int32_t NAPI_ARGS_ONE = 1;
constexpr int32_t NAPI_ARGS_TWO = 2;
constexpr int32_t NAPI_ARGS_THREE = 3;
constexpr int32_t NAPI_BUF_LENGTH  = 256;
}

OHOS::sptr<ISpatialLocationCallback> SpatialNapi::callback_ = nullptr;
thread_local napi_ref SpatialNapi::sConstructor_ = nullptr;

void SpatialNativeCallback::OnDirectionChanged(const DirectionResponse &directionRes)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "%{public}s direction=%{public}d", __func__, static_cast<int>(directionRes.directionType));
    uv_loop_s *loop = nullptr;
    napi_get_uv_event_loop(env_, &loop);
    if (loop == nullptr) {
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (loop == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "loop is nullptr");
        return;
    }
    static DirectionResponse staticiDrectionRes;
    staticiDrectionRes.directionType = directionRes.directionType;
    int ret = uv_queue_work(loop, work, [] (uv_work_t *work) {}, [] (uv_work_t *work, int status) {
        SpatialNapi *spatialNapi = SpatialNapi::GetSpatialNapi();
        if (spatialNapi == nullptr) {
            SPATIAL_LOGE(MODULE_JS_NAPI, "spatialNapi is nullptr");
            return;
        }
        spatialNapi->OnDirectionChangedDone(staticiDrectionRes);
        delete work;
    });
    if (ret != 0) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "Failed to execute work queue");
        delete work;
    }
}

void SpatialNativeCallback::OnDistanceChanged(const DistanceResponse &distanceRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s distance: %{public}f", __func__, distanceRes.distance);
    uv_loop_s *loop = nullptr;
    napi_get_uv_event_loop(env_, &loop);
    if (loop == nullptr) {
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (loop == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "loop is nullptr");
        return;
    }
    static DistanceResponse staticDistanceRes;
    staticDistanceRes.distance = distanceRes.distance;
    int ret = uv_queue_work(loop, work, [] (uv_work_t *work) {}, [] (uv_work_t *work, int status) {
        SpatialNapi *spatialNapi = SpatialNapi::GetSpatialNapi();
        if (spatialNapi == nullptr) {
            SPATIAL_LOGE(MODULE_JS_NAPI, "spatialNapi is nullptr");
            return;
        }
        spatialNapi->OnDistanceChangedDone(staticDistanceRes);
        delete work;
    });
    if (ret != 0) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "Failed to execute work queue");
        delete work;
    }
}

void SpatialNativeCallback::OnAngleChanged(const AngleResponse &angleRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    uv_loop_s *loop = nullptr;
    napi_get_uv_event_loop(env_, &loop);
    if (loop == nullptr) {
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (loop == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "loop is nullptr");
        return;
    }
    static AngleResponse staticAngleRes;
    staticAngleRes.angle = angleRes.angle;
    int ret = uv_queue_work(loop, work, [] (uv_work_t *work) {}, [] (uv_work_t *work, int status) {
        SpatialNapi *spatialNapi = SpatialNapi::GetSpatialNapi();
        if (spatialNapi == nullptr) {
            SPATIAL_LOGE(MODULE_JS_NAPI, "spatialNapi is nullptr");
            return;
        }
        spatialNapi->OnAngleChangedDone(staticAngleRes);
        delete work;
    });
    if (ret != 0) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "Failed to execute work queue");
        delete work;
    }
}

void SpatialNativeCallback::OnInnerOuterChanged(const InnerOuterResponse &ioRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    uv_loop_s *loop = nullptr;
    napi_get_uv_event_loop(env_, &loop);
    if (loop == nullptr) {
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (loop == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "loop is nullptr");
        return;
    }
    static InnerOuterResponse staticIoRes;
    staticIoRes.innerOuterType = ioRes.innerOuterType;
    int ret = uv_queue_work(loop, work, [] (uv_work_t *work) {}, [] (uv_work_t *work, int status) {
        SpatialNapi *spatialNapi = SpatialNapi::GetSpatialNapi();
        if (spatialNapi == nullptr) {
            SPATIAL_LOGE(MODULE_JS_NAPI, "spatialNapi is nullptr");
            return;
        }
        spatialNapi->OnInnerOuterChangedDone(staticIoRes);
        delete work;
    });
    if (ret != 0) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "Failed to execute work queue");
        delete work;
    }
}

void SpatialNativeCallback::OnExistChanged(const ExistResponse &existRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    uv_loop_s *loop = nullptr;
    napi_get_uv_event_loop(env_, &loop);
    if (loop == nullptr) {
        return;
    }
    uv_work_t *work = new (std::nothrow) uv_work_t;
    if (loop == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "loop is nullptr");
        return;
    }
    static ExistResponse staticExistRes;
    staticExistRes.existType = existRes.existType;
    int ret = uv_queue_work(loop, work, [] (uv_work_t *work) {}, [] (uv_work_t *work, int status) {
        SpatialNapi *spatialNapi = SpatialNapi::GetSpatialNapi();
        if (spatialNapi == nullptr) {
            SPATIAL_LOGE(MODULE_JS_NAPI, "spatialNapi is nullptr");
            return;
        }
        spatialNapi->OnExistChangedDone(staticExistRes);
        delete work;
    });
    if (ret != 0) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "Failed to execute work queue");
        delete work;
    }
}

void SpatialNapi::JsToJsonObject(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, nlohmann::json &jsonObj)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "JsToJsonObject in.");
    bool hasProperty = false;
    NAPI_CALL_RETURN_VOID(env, napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (!hasProperty) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "spatialawareness napi js to str no property: %s", fieldStr.c_str());
        return;
    }

    napi_value jsonField = nullptr;
    napi_get_named_property(env, object, fieldStr.c_str(), &jsonField);
    napi_valuetype jsValueType = napi_undefined;
    napi_value jsProNameList = nullptr;
    uint32_t jsProCount = 0;
    napi_get_property_names(env, jsonField, &jsProNameList);
    napi_get_array_length(env, jsProNameList, &jsProCount);
    SPATIAL_LOGI(MODULE_JS_NAPI, "Property size=%d.", jsProCount);

    napi_value jsProName = nullptr;
    napi_value jsProValue = nullptr;
    for (uint32_t index = 0; index < jsProCount; index++) {
        napi_get_element(env, jsProNameList, index, &jsProName);
        std::string strProName = JsObjectToString(env, jsProName);
        napi_get_named_property(env, jsonField, strProName.c_str(), &jsProValue);
        napi_typeof(env, jsProValue, &jsValueType);
        CheckJsNapiType(env, jsValueType, jsProValue, jsonObj, strProName);
    }
}

void SpatialNapi::CheckJsNapiType(const napi_env &env, const napi_valuetype &jsValueType,
    napi_value jsProValue, nlohmann::json &jsonObj, std::string strProName)
{
    switch (jsValueType) {
        case napi_string: {
            std::string natValue = JsObjectToString(env, jsProValue);
            SPATIAL_LOGI(MODULE_JS_NAPI,
                "Property name=%s, string, value=%s", strProName.c_str(), natValue.c_str());
            jsonObj[strProName] = natValue;
            break;
        }
        case napi_boolean: {
            bool elementValue = false;
            napi_get_value_bool(env, jsProValue, &elementValue);
            SPATIAL_LOGI(MODULE_JS_NAPI, "Property name=%s, boolean, value=%d.", strProName.c_str(), elementValue);
            jsonObj[strProName] = elementValue;
            break;
        }
        case napi_number: {
            int32_t elementValue = 0;
            if (napi_get_value_int32(env, jsProValue, &elementValue) != napi_ok) {
                SPATIAL_LOGE(MODULE_JS_NAPI, "Property name=%s, Property int32_t parse error", strProName.c_str());
            } else {
                jsonObj[strProName] = elementValue;
                SPATIAL_LOGI(MODULE_JS_NAPI,
                    "Property name=%s, number, value=%d.", strProName.c_str(), elementValue);
            }
            break;
        }
        default: {
            SPATIAL_LOGE(MODULE_JS_NAPI, "Property name=%s, value type not support.", strProName.c_str());
            break;
        }
    }
}

void SpatialNapi::JsToDmBuffer(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, uint8_t **bufferPtr, int32_t &bufferLen)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "JsToDmBuffer in.");
    bool hasProperty = false;
    NAPI_CALL_RETURN_VOID(env, napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (!hasProperty) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "spatialawareness napi js to str no property: %s", fieldStr.c_str());
        return;
    }

    napi_value field = nullptr;
    napi_get_named_property(env, object, fieldStr.c_str(), &field);
    napi_typedarray_type type = napi_uint8_array;
    size_t length = 0;
    napi_value buffer = nullptr;
    size_t offset = 0;
    uint8_t *data = nullptr;
    napi_get_typedarray_info(env, field, &type,
        &length, reinterpret_cast<void **>(&data), &buffer, &offset);
    if (type != napi_uint8_array || length == 0 || data == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "Invaild AppIconInfo");
        return;
    }
    *bufferPtr = (uint8_t*)calloc(sizeof(uint8_t), length);
    if (*bufferPtr == nullptr) {
        SPATIAL_LOGE(MODULE_JS_NAPI,
            "low memory, calloc return nullptr, length is %d,  filed %s", length, fieldStr.c_str());
        return;
    }
    if (memcpy_s(*bufferPtr, length, data, length) != 0) {
        SPATIAL_LOGE(MODULE_JS_NAPI, "memcpy_s failed, filed %s", fieldStr.c_str());
        free(*bufferPtr);
        *bufferPtr = nullptr;
        return;
    }
    bufferLen = length;
}

void SpatialNapi::JsObjectToInt(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, int32_t &fieldRef)
{
    bool hasProperty = false;
    NAPI_CALL_RETURN_VOID(env, napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (hasProperty) {
        napi_value field = nullptr;
        napi_valuetype valueType = napi_undefined;

        napi_get_named_property(env, object, fieldStr.c_str(), &field);
        NAPI_CALL_RETURN_VOID(env, napi_typeof(env, field, &valueType));
        NAPI_ASSERT_RETURN_VOID(env, valueType == napi_number, "Wrong argument type. Number expected.");
        napi_get_value_int32(env, field, &fieldRef);
    } else {
        SPATIAL_LOGE(MODULE_JS_NAPI, "spatialawareness napi js to int no property: %s", fieldStr.c_str());
    }
}

std::string SpatialNapi::JsObjectToString(const napi_env &env, const napi_value &param)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "JsObjectToString in.");
    size_t size = 0;
    if (napi_get_value_string_utf8(env, param, nullptr, 0, &size) != napi_ok) {
        return "";
    }
    if (size == 0) {
        return "";
    }
    char *buf = new (std::nothrow) char[size + 1];
    if (buf == nullptr) {
        return "";
    }
    memset_s(buf, (size + 1), 0, (size + 1));
    bool rev = napi_get_value_string_utf8(env, param, buf, size + 1, &size) == napi_ok;

    std::string value;
    if (rev) {
        value = buf;
    } else {
        value = "";
    }
    delete[] buf;
    buf = nullptr;
    return value;
}

void SpatialNapi::JsObjectToString(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, char *dest, const int32_t destLen)
{
    bool hasProperty = false;
    NAPI_CALL_RETURN_VOID(env, napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (hasProperty) {
        napi_value field = nullptr;
        napi_valuetype valueType = napi_undefined;

        napi_get_named_property(env, object, fieldStr.c_str(), &field);
        NAPI_CALL_RETURN_VOID(env, napi_typeof(env, field, &valueType));
        NAPI_ASSERT_RETURN_VOID(env, valueType == napi_string, "Wrong argument type. String expected.");
        size_t result = 0;
        NAPI_CALL_RETURN_VOID(env, napi_get_value_string_utf8(env, field, dest, destLen, &result));
    } else {
        SPATIAL_LOGE(MODULE_JS_NAPI, "spatialawareness napi js to str no property: %s", fieldStr.c_str());
    }
}

void SpatialNapi::JsObjectToBool(const napi_env &env, const napi_value &object,
    const std::string &fieldStr, bool &fieldRef)
{
    bool hasProperty = false;
    NAPI_CALL_RETURN_VOID(env, napi_has_named_property(env, object, fieldStr.c_str(), &hasProperty));
    if (hasProperty) {
        napi_value field = nullptr;
        napi_valuetype valueType = napi_undefined;

        napi_get_named_property(env, object, fieldStr.c_str(), &field);
        NAPI_CALL_RETURN_VOID(env, napi_typeof(env, field, &valueType));
        NAPI_ASSERT_RETURN_VOID(env, valueType == napi_boolean, "Wrong argument type. Bool expected.");
        napi_get_value_bool(env, field, &fieldRef);
    } else {
        SPATIAL_LOGE(MODULE_JS_NAPI, "spatialawareness napi js to bool no property: %s", fieldStr.c_str());
    }
}

void SpatialNapi::SetValueInt32(const napi_env &env, const std::string &fieldStr, const int32_t intValue,
    napi_value &result)
{
    napi_value value = nullptr;
    napi_create_int32(env, intValue, &value);
    napi_set_named_property(env, result, fieldStr.c_str(), value);
}

void SpatialNapi::SetValueUtf8String(const napi_env &env, const std::string &fieldStr, const std::string &str,
    napi_value &result)
{
    napi_value value = nullptr;
    if (napi_create_string_utf8(env, str.c_str(), NAPI_AUTO_LENGTH, &value) == napi_ok) {
        napi_set_named_property(env, result, fieldStr.c_str(), value);
    }
}

void SpatialNapi::SetValueDouble(const napi_env &env, const std::string &fieldStr, const double &doubleValue,
    napi_value &result)
{
    napi_value value = nullptr;
    napi_create_double(env, doubleValue, &value);
    napi_set_named_property(env, result, fieldStr.c_str(), value);
}

void SpatialNapi::JsToSpatialDeviceInfo(const napi_env &env, const napi_value &object,
    SpatialDeviceInfo &info)
{
    char deviceId[96];
    char deviceName[128];
    JsObjectToString(env, object, "deviceId", deviceId, sizeof(deviceId));
    JsObjectToString(env, object, "deviceName", deviceName, sizeof(deviceName));
    int32_t deviceType = -1;
    JsObjectToInt(env, object, "deviceType", deviceType);
    SpatialDeviceType deviceTypeId = (SpatialDeviceType)deviceType;
    SpatialDeviceInfo deviceInfo(deviceId, deviceName, deviceTypeId);
    info = deviceInfo;
}

SpatialNapi *SpatialNapi::GetSpatialNapi()
{
    return g_spatialNapi;
}

void SpatialNapi::CreateSpatialCallback(napi_env env,
    const SpatialLocationMode &spatialMode, const SpatialDeviceInfo &info)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "spatialMode %{public}d", spatialMode);
    if (callback_ == nullptr) {
        callback_ = new SpatialNativeCallback(env);
    }
    SpatialAwarenessMgrClient::GetInstance().SubscribeSpatialLocationRelationship(spatialMode, info, callback_);
    return;
}

void SpatialNapi::ReleaseSpatialCallback(const SpatialLocationMode &spatialMode, const SpatialDeviceInfo &info)
{
    if (callback_ != nullptr) {
        SpatialAwarenessMgrClient::GetInstance().UnSubscribeSpatialLocationRelationship(spatialMode, info, callback_);
    }
    return;
}

void SpatialNapi::Mapping()
{
    g_ModeMapping.insert(std::make_pair(SpatialNapiString::NAPI_DIRECTION_MODE, SpatialLocationMode::MODE_DIRECTION));
    g_ModeMapping.insert(std::make_pair(SpatialNapiString::NAPI_DISTANCE_MODE, SpatialLocationMode::MODE_DISTANCE));
    g_ModeMapping.insert(std::make_pair(SpatialNapiString::NAPI_ANGLE_MODE, SpatialLocationMode::MODE_ANGLE));
    g_ModeMapping.insert(std::make_pair(SpatialNapiString::NAPI_INNEROUTER_MODE,
        SpatialLocationMode::MODE_INNER_OUTER));
    g_ModeMapping.insert(std::make_pair(SpatialNapiString::NAPI_EXIST_MODE, SpatialLocationMode::MODE_EXIST));
    g_DrectionMap.insert(std::make_pair(DirectionType::TYPE_FRONT, SpatialNapiString::FRONT));
    g_DrectionMap.insert(std::make_pair(DirectionType::TYPE_BACK, SpatialNapiString::BACK));
    g_DrectionMap.insert(std::make_pair(DirectionType::TYPE_LEFT, SpatialNapiString::LEFT));
    g_DrectionMap.insert(std::make_pair(DirectionType::TYPE_RIGHT, SpatialNapiString::RIGHT));
    g_DrectionMap.insert(std::make_pair(DirectionType::TYPE_UP, SpatialNapiString::UP));
    g_DrectionMap.insert(std::make_pair(DirectionType::TYPE_DOWN, SpatialNapiString::DOWN));
}

void SpatialNapi::OnDirectionChangedDone(const DirectionResponse &directionRes)
{
    SPATIAL_LOGI(MODULE_SERVICE,
        "%{public}s direction=%{public}d", __func__, static_cast<int>(directionRes.directionType));
    napi_value result = nullptr;
    napi_create_object(env_, &result);
    napi_value directionObj = nullptr;
    napi_create_object(env_, &directionObj);
    auto iter = g_DrectionMap.find(directionRes.directionType);
    if (iter != g_DrectionMap.end()) {
        SetValueUtf8String(env_, "direction", iter->second, directionObj);
        napi_set_named_property(env_, result, "directionRes", directionObj);
        SPATIAL_LOGI(MODULE_SERVICE, "%{public}s napi.direction=%{public}s", __func__, iter->second.c_str());
        OnEvent(SpatialNapiString::NAPI_DIRECTION_MODE, NAPI_ARGS_ONE, &result);
    }
}

void SpatialNapi::OnDistanceChangedDone(const DistanceResponse &distanceRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s distance %{public}f", __func__, distanceRes.distance);
    napi_value result = nullptr;
    napi_create_object(env_, &result);
    napi_value distanceObj = nullptr;
    napi_create_object(env_, &distanceObj);
    SetValueDouble(env_, "distance", (double)distanceRes.distance, distanceObj);
    napi_set_named_property(env_, result, "distanceRes", distanceObj);
    OnEvent(SpatialNapiString::NAPI_DISTANCE_MODE, NAPI_ARGS_ONE, &result);
}

void SpatialNapi::OnAngleChangedDone(const AngleResponse &angleRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s angle %{public}f", __func__, angleRes.angle);
    napi_value result = nullptr;
    napi_create_object(env_, &result);
    napi_value angleObj = nullptr;
    napi_create_object(env_, &angleObj);
    SetValueDouble(env_, "angle", (double)angleRes.angle, angleObj);
    napi_set_named_property(env_, result, "angleRes", angleObj);
    OnEvent(SpatialNapiString::NAPI_ANGLE_MODE, NAPI_ARGS_ONE, &result);
}

void SpatialNapi::OnInnerOuterChangedDone(const InnerOuterResponse &ioRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    napi_value result = nullptr;
    napi_create_object(env_, &result);
    napi_value ioObj = nullptr;
    napi_create_object(env_, &ioObj);
    SetValueInt32(env_, "innerouter", (int)ioRes.innerOuterType, ioObj);
    napi_set_named_property(env_, result, "ioRes", ioObj);
    OnEvent(SpatialNapiString::NAPI_INNEROUTER_MODE, NAPI_ARGS_ONE, &result);
}

void SpatialNapi::OnExistChangedDone(const ExistResponse &existRes)
{
    SPATIAL_LOGI(MODULE_SERVICE, "%{public}s enter", __func__);
    napi_value result = nullptr;
    napi_create_object(env_, &result);
    napi_value existObj = nullptr;
    napi_create_object(env_, &existObj);
    SetValueInt32(env_, "exist", (int)existRes.existType, existObj);
    napi_set_named_property(env_, result, "existRes", existObj);
    OnEvent(SpatialNapiString::NAPI_EXIST_MODE, NAPI_ARGS_ONE, &result);
}

napi_value SpatialNapi::SubscriptionSpatialRelationship(napi_env env, napi_callback_info info)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "%{public}s enter", __func__);
    size_t argc = NAPI_ARGS_THREE;
    napi_value args[NAPI_ARGS_THREE] = {0};
    napi_value jsthis;
    void *data = nullptr;

    napi_status status = napi_get_cb_info(env, info, &argc, args, &jsthis, &data);
    NAPI_ASSERT(env, (status == napi_ok) && (argc >= NAPI_ARGS_THREE), "Wrong number of arguments, required 3");

    napi_valuetype spatialModeType = napi_undefined;
    napi_typeof(env, args[NAPI_ARGS_ZERO], &spatialModeType);
    NAPI_ASSERT(env, spatialModeType == napi_string, "type mismatch for parameter 1");

    napi_valuetype deviceInfoType = napi_undefined;
    napi_typeof(env, args[NAPI_ARGS_ONE], &deviceInfoType);
    NAPI_ASSERT(env, deviceInfoType == napi_object, "type mismatch for parameter 2");

    napi_valuetype eventHandleType = napi_undefined;
    napi_typeof(env, args[NAPI_ARGS_TWO], &eventHandleType);
    NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 3");
    size_t modeLen = 0;
    napi_get_value_string_utf8(env, args[NAPI_ARGS_ZERO], nullptr, 0, &modeLen);

    NAPI_ASSERT(env, modeLen > 0, "modeLen == 0");
    NAPI_ASSERT(env, modeLen < NAPI_BUF_LENGTH, "modeLen >= MAXLEN");
    char mode[NAPI_BUF_LENGTH] = {0};
    napi_get_value_string_utf8(env, args[NAPI_ARGS_ZERO], mode, modeLen + 1, &modeLen);
    std::string eventMode = mode;

    SpatialDeviceInfo deviceInfo;
    JsToSpatialDeviceInfo(env, args[NAPI_ARGS_ONE], deviceInfo);

    if (g_spatialNapi == nullptr) {
        g_spatialNapi = new SpatialNapi(env, jsthis);
        napi_wrap(env, jsthis, reinterpret_cast<void *>(g_spatialNapi),
            [](napi_env env, void *data, void *hint) {
                (void)env;
                (void)hint;
                SpatialNapi *spatial = (SpatialNapi *)data;
                delete spatial;
            }, nullptr, &(g_spatialNapi->wrapper_));
        NAPI_ASSERT(env, g_spatialNapi != nullptr, "g_spatialNapi is nullptr");
    }
    if (g_spatialNapi != nullptr) {
        g_spatialNapi->On(eventMode, args[NAPI_ARGS_TWO]);
    }
    return CreateCallback(env, eventMode, deviceInfo);
}

napi_value SpatialNapi::CreateCallback(napi_env env, const std::string &eventMode, const SpatialDeviceInfo &deviceInfo)
{
    SPATIAL_LOGI(MODULE_JS_NAPI,
        "eventMode:%{public}s, deviceName:%{public}s", eventMode.c_str(), deviceInfo.GetDeviceName().c_str());
    auto iter = g_ModeMapping.find(eventMode);
    if (iter != g_ModeMapping.end()) {
        CreateSpatialCallback(env, iter->second, deviceInfo);
    }

    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

napi_value SpatialNapi::UnSubscriptionSpatialRelationship(napi_env env, napi_callback_info info)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "%{public}s enter", __func__);

    size_t argc = NAPI_ARGS_THREE;
    napi_value args[NAPI_ARGS_THREE] = {0};
    napi_value jsthis;
    void *data = nullptr;

    napi_status status = napi_get_cb_info(env, info, &argc, args, &jsthis, &data);
    NAPI_ASSERT(env, (status == napi_ok) && (argc >= NAPI_ARGS_THREE), "Wrong number of arguments, required 3");

    napi_valuetype spatialModeType = napi_undefined;
    napi_typeof(env, args[NAPI_ARGS_ZERO], &spatialModeType);
    NAPI_ASSERT(env, spatialModeType == napi_string, "type mismatch for parameter 1");

    napi_valuetype deviceInfoType = napi_undefined;
    napi_typeof(env, args[NAPI_ARGS_ONE], &deviceInfoType);
    NAPI_ASSERT(env, deviceInfoType == napi_object, "type mismatch for parameter 2");

    napi_valuetype eventHandleType = napi_undefined;
    napi_typeof(env, args[NAPI_ARGS_TWO], &eventHandleType);
    NAPI_ASSERT(env, eventHandleType == napi_function, "type mismatch for parameter 3");

    size_t modeLen = 0;
    napi_get_value_string_utf8(env, args[NAPI_ARGS_ZERO], nullptr, 0, &modeLen);

    NAPI_ASSERT(env, modeLen > 0, "modeLen == 0");
    NAPI_ASSERT(env, modeLen < NAPI_BUF_LENGTH, "modeLen >= MAXLEN");
    char mode[NAPI_BUF_LENGTH] = {0};
    napi_get_value_string_utf8(env, args[NAPI_ARGS_ZERO], mode, modeLen + 1, &modeLen);
    std::string eventMode = mode;

    SpatialDeviceInfo deviceInfo;
    JsToSpatialDeviceInfo(env, args[NAPI_ARGS_ONE], deviceInfo);

    g_spatialNapi->Off(eventMode, args[NAPI_ARGS_TWO]);

    auto iter = g_ModeMapping.find(eventMode);
    if (iter != g_ModeMapping.end()) {
        ReleaseSpatialCallback(iter->second, deviceInfo);
    }
    napi_value result = nullptr;
    napi_get_undefined(env, &result);
    return result;
}

napi_value SpatialNapi::ModeInit(napi_env env, napi_value exports)
{
    napi_value obj = nullptr;
    napi_create_object(env, &obj);
    SetNamedPropertyByStr(env, obj, SpatialNapiString::NAPI_DIRECTION_MODE, "DIRECTION");
    SetNamedPropertyByStr(env, obj, SpatialNapiString::NAPI_DISTANCE_MODE, "DISTANCE");
    SetNamedPropertyByStr(env, obj, SpatialNapiString::NAPI_ANGLE_MODE, "ANGLE");
    SetNamedPropertyByStr(env, obj, SpatialNapiString::NAPI_INNEROUTER_MODE, "INNER_OUTER");
    SetNamedPropertyByStr(env, obj, SpatialNapiString::NAPI_EXIST_MODE, "EXIST");

    napi_property_descriptor exportFuncs[] = {DECLARE_NAPI_PROPERTY("PositionRelation", obj)};
    napi_define_properties(env, exports, sizeof(exportFuncs) / sizeof(*exportFuncs), exportFuncs);

    return exports;
}

void SpatialNapi::SetNamedPropertyByStr(napi_env env,
    napi_value dstObj, const std::string &objName, const char *propName)
{
    napi_value prop = nullptr;
    if (napi_create_string_utf8(env, objName.c_str(), NAPI_AUTO_LENGTH, &prop) == napi_ok) {
        napi_set_named_property(env, dstObj, propName, prop);
    }
}

napi_value SpatialNapi::Init(napi_env env, napi_value exports)
{
    napi_property_descriptor desc[] = {
        DECLARE_NAPI_STATIC_FUNCTION("on", SubscriptionSpatialRelationship),
        DECLARE_NAPI_STATIC_FUNCTION("off", UnSubscriptionSpatialRelationship)
    };

    SPATIAL_LOGI(MODULE_JS_NAPI, "SpatialNapi::Init() is called!");
    NAPI_CALL(env, napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc));
    ModeInit(env, exports);
    Mapping();
    return exports;
}

SpatialNapi::SpatialNapi(napi_env env, napi_value thisVar) : SpatialNativeEvent(env, thisVar)
{
    env_ = env;
    wrapper_ = nullptr;
}

SpatialNapi::~SpatialNapi()
{
    if (wrapper_ != nullptr) {
        napi_delete_reference(env_, wrapper_);
    }
}

/*
 * Function registering all props and functions of ohos.msdp
 */
static napi_value Export(napi_env env, napi_value exports)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "Export() is called!");
    SpatialNapi::Init(env, exports);
    return exports;
}

/*
 * module define
 */
static napi_module g_Module = {
    .nm_version = 1,
    .nm_flags = 0,
    .nm_filename = nullptr,
    .nm_register_func = Export,
    .nm_modname = "spatialawareness",
    .nm_priv = ((void *)0),
    .reserved = {0}
};

/*
 * module register
 */
extern "C" __attribute__((constructor)) void RegisterModule(void)
{
    SPATIAL_LOGI(MODULE_JS_NAPI, "RegisterModule() is called!");
    napi_module_register(&g_Module);
}